﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace MessengerBot
{
    /// <summary>
    /// This class maps text command names to their respective <see cref="ICommand"/> implementation.
    /// </summary>
    public class CommandMap
    {
        /// <summary>
        /// This field holds a mapping from the command name to its <see cref="Type"/> object for the class
        /// that implements the command.
        /// </summary>
        private readonly Dictionary<string, Type> _CommandTypes = new Dictionary<string, Type>();

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandMap"/> class.
        /// </summary>
        /// <param name="commandBasedSystems">
        /// A collection of <see cref="ICommandBasedSystem"/> objects that this <see cref="CommandMap"/> should manage.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="commandBasedSystems"/> is <c>null</c>.</para>
        /// </exception>
        public CommandMap(IEnumerable<ICommandBasedSystem> commandBasedSystems)
        {
            if (commandBasedSystems == null)
                throw new ArgumentNullException("commandBasedSystems");

            var commandGroups = from system in commandBasedSystems
                                where system != null
                                from commandType in system.GetCommands()
                                where commandType != null
                                where commandType.IsDefined(typeof(CommandAttribute), true)
                                from attr in commandType.GetCustomAttributes(typeof(CommandAttribute), true).OfType<CommandAttribute>()
                                let name = attr.Name
                                select new
                                {
                                    name,
                                    commandType,
                                    system
                                }
                                into systemCommand group systemCommand by systemCommand.name
                                into g select g;

            foreach (var commandGroup in commandGroups)
            {
                if (commandGroup.Count() == 1)
                {
                    var command = commandGroup.First();
                    _CommandTypes[command.name] = command.commandType;
                }
                else
                {
                    // TODO: Create better unique names, perhaps system-command ?
                    int index = 0;
                    foreach (var command in commandGroup)
                    {
                        index++;
                        _CommandTypes[command.name + index] = command.commandType;
                    }
                }
            }
        }

        /// <summary>
        /// Gets a collection of all the command <see cref="Type"/>s.
        /// </summary>
        /// <returns>
        /// A collection of all the command <see cref="Type"/>s.
        /// </returns>
        public IEnumerable<Type> GetAllCommands()
        {
            return _CommandTypes.Values.OfType<Type>().Distinct();
        }

        /// <summary>
        /// Attempts to get the command <see cref="Type"/> by its name.
        /// </summary>
        /// <param name="commandName">
        /// The name of the command to get.
        /// </param>
        /// <returns>
        /// The <see cref="Type"/> of the class that implements the command with the name <paramref name="commandName"/>;
        /// or <c>null</c> if there is no such command.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <para><paramref name="commandName"/> is <c>null</c> or empty.</para>
        /// </exception>
        public Type GetCommandByName(string commandName)
        {
            if (string.IsNullOrWhiteSpace(commandName))
                throw new ArgumentNullException("commandName");

            Type result;
            _CommandTypes.TryGetValue(commandName, out result);
            return result;
        }
    }
}